package com.zwps.common.controller.shiro.realm;

import lombok.extern.slf4j.Slf4j;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.web.subject.WebSubject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.zwps.common.api.CommonUserAPI;
import com.zwps.common.api.model.vo.LoginInfoVO;
import com.zwps.common.api.model.vo.LoginUserVO;
import com.zwps.common.controller.constant.HttpHeaderConstant;
import com.zwps.common.controller.shiro.authc.JwtToken;
import com.zwps.common.core.context.LoginInfoContext;
import com.zwps.common.core.context.LoginedMarkContext;
import com.zwps.common.core.exception.AbstractCommonException;
import com.zwps.common.core.exception.AuthenticationFailureException;

import java.util.Set;

import javax.servlet.http.HttpServletRequest;

@Slf4j
public class ShiroRealm extends AuthorizingRealm {

    @Lazy
    @Autowired
    private CommonUserAPI userAPI;

    @Autowired
    private ObjectMapper objectMapper;
    
    /**
     * 必须重写此方法，不然Shiro会报错
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    /**
     * 权限信息认证(包括角色以及权限)是用户访问controller的时候才进行验证(redis存储的此处权限信息)
     * 触发检测用户权限时才会调用此方法，例如checkRole,checkPermission
     *
     * @param principals 身份信息
     * @return AuthorizationInfo 权限信息
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        if (principals == null) {
            log.debug("principals is null");
            return null;
        }
        log.info("doGetAuthorizationInfo...");
        // TODO: 由于权限验证和相关的接口权限表还未实现，此处代码是有问题的
        LoginUserVO sysUser = (LoginUserVO) principals.getPrimaryPrincipal();
        String uid = sysUser.getId();
        // 设置用户拥有的角色集合，比如“admin,test”
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(userAPI.getUserRoleCodes(uid));
        // 设置用户拥有的权限集合，比如“sys:role:add,sys:user:add”
        Set<String> permissionSet = userAPI.getUserPermissionCodes(uid);
        info.addStringPermissions(permissionSet);
        return info;
    }

    /**
     * 用户信息认证是在用户进行登录的时候进行验证 也就是说验证用户输入的账号和密码是否正确，错误抛出异常
     *
     * @param auth 用户登录的账号密码信息
     * @return 返回封装了用户信息的 AuthenticationInfo 实例
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
        String token = (String) auth.getCredentials();
        if (token == null) {
            log.error("Invalid token, token: {}", token);
            throw new AuthenticationException("无效的Token", new AuthenticationFailureException("无效的Token"));
        }
        // 处理注入的request获取getAttribute属性时出现以下错误
        // No thread-bound request found: Are you referring to request attributes
        // outside of an actual web request, or processing a request outside of the
        // originally receiving thread? If you are actually operating within a web
        // request and still receive this message, your code is probably running outside
        // of DispatcherServlet: In this case, use RequestContextListener or
        // RequestContextFilter to expose the current request.
        HttpServletRequest request = (HttpServletRequest) ((WebSubject) SecurityUtils.getSubject()).getServletRequest();
        HttpHeaderConstant.getAndSetRequestId(request);
        // 校验token有效性
        LoginInfoVO loginInfo = null;
        if (HttpHeaderConstant.isFeignRequest(request)) {
            String loginInfoStr = request.getHeader(HttpHeaderConstant.HEADER_LOGIN_INFO);
            if (loginInfoStr != null) {
                try {
                    loginInfo = objectMapper.readValue(loginInfoStr, LoginInfoVO.class);
                } catch (Exception e) {
                    log.error("ObjectMapper read value error: ", e);
                }
            }
        }
        
        try {
            if (loginInfo == null) {
                loginInfo = userAPI.authAndReturnLoginInfo(token);
            }
        } catch (AuthenticationFailureException e) {
            throw new AuthenticationException(e.getMessage(), e);
        } catch (AbstractCommonException e) {
            throw new AuthenticationException(e.getMessage(), e);
        }
        LoginedMarkContext.login();
        LoginInfoContext.setLoginInfo(loginInfo);// 注入当前登录用户信息
        return new SimpleAuthenticationInfo(loginInfo.getUser(), token, getName());
    }

    /**
     * 清除当前用户的权限认证缓存
     *
     * @param principals 权限信息
     */
    @Override
    public void clearCache(PrincipalCollection principals) {
        super.clearCache(principals);
    }

}
